package org.hackystat.devcathlon.engine.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.devcathlon.engine.match.Match;
import org.hackystat.devcathlon.engine.match.MatchException;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Deducts points for a failed CI build.
 * <p>
 * Requires the following properties in the configuration.xml file for this event:
 * <ul>
 * <li>WakeupInterval: How frequently this event is invoked for looking for a failed CI build.
 * <li>FailedWeight: The integer given for one failed CI build.
 * </ul>
 * <p>
 * Specification:
 * <ul>
 * <li>Checks for "continuous.integration" build sensor data since the last wakeup interval for all
 * build sensor data instance. Records the first timestamp associated with that sensor data.
 * <li>Retrieves all build sensor data. Finds out if there are any successful and failed builds.
 * <li>Base Score: 0 if no previous build data exists. Otherwise base score is -5 depending failed
 * CI build since wakeup of this event.
 * </ul>
 * 
 * @author Ka Hung Phillip Lau & Robin Ranqueno
 * 
 */
public class KeepTheRepositoryCleanHourly extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;

  /** Name of the event. */
  public static final String eventName = "Keep The Repository Clean Hourly";

  /** Description of the event. */
  public static final String description = "Penalize (or award) developers for committing code "
      + "that fails (or passes) a continuous integration build. ";

  /** The actual failed build point weight. */
  private int failedWeight;
  /** The property key for failedWeight. */
  public static final String failedWeightKey = "FailedWeight";

  /**
   * Creates a new event for Keep The Repository Clean. Ensures all required properties are
   * available.
   * 
   * @throws EventException If the configuration cannot be created.
   */
  public KeepTheRepositoryCleanHourly() throws EventException {
    super(eventName, description);
    try {
      failedWeight = Integer.parseInt(configManager.getProperty(eventName, failedWeightKey));
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
  }

  /**
   * Returns the score for the event.
   * 
   * @param score The current score for event.
   * @return score The total score for event.
   */
  @Override
  public int computePoints(EventScore score) {
    return score.getBaseScore();
  }

  /**
   * Calculates the score for the event, regular score plus bonus score.
   * 
   * @param match The name of match.
   * @param team The name of team.
   * @param timestamp The timestamp given when this event is invoke.
   * @return score The total score for the event.
   * @throws EventException If there is no build data.
   * @throws MatchException If an error occurs
   */
  @Override
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException, MatchException {

    int totalPoints = 0;

    // [0] create the default score, which awards no points.
    EventScore score = new EventScore(timestamp, match, team);
    // [1] find the project and the sensorbase client.
    Project project = match.getProject(team);
    SensorBaseClient client = match.getSensorBaseClient(team);
    // [2] get the Build sensor data for the previous interval of time.
    String projectName = project.getName();
    String owner = project.getOwner();
    String sdt = "Build";
    XMLGregorianCalendar endTime = timestamp;
    XMLGregorianCalendar startTime = Tstamp.incrementMinutes(endTime, wakeupInterval * -1);
    SensorDataIndex index;

    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting build sensor data for match %s and team %s", match.getName(), team
            .getName()));
    try {
      index = client.getProjectSensorData(owner, projectName, startTime, endTime, sdt);
    }
    catch (Exception e) {
      throw new EventException("Error retrieving build data.", e);
    }
    // [3] Deducts points on a failed CI build is any
    try {
      totalPoints = getEventPoints(client, index);
      score.setBaseScore(totalPoints);
    }
    catch (Exception e) {
      score.setBaseScore(totalPoints);
      return score;
    }

    return score;
  }

  /**
   * Finds if there are any CI failed builds for the last hour.
   * 
   * @param client The client name.
   * @param index The name of index
   * @return score The total score for event.
   * @throws Exception If there is no build data.
   */
  private int getEventPoints(SensorBaseClient client, SensorDataIndex index) throws Exception {
    int failedBuilds = 0;
    for (SensorDataRef ref : index.getSensorDataRef()) {
      SensorData data = client.getSensorData(ref);
      Property buildTypeProperty = data.findProperty("Type");
      if ((buildTypeProperty != null)
          && (buildTypeProperty.getValue().equals("continuous.integration"))) {

        Property buildResultProperty = data.findProperty("Result");
        if (buildResultProperty != null && buildResultProperty.getValue().equals("Failure")) {
          failedBuilds++;
        }
      }
    }
    if (failedBuilds > 0) {
      return this.failedWeight;
    }

    return 0;

  }

}
